#include "stdafx.h"

#include <MyControls/BezierEditor.h>
#include <MyControls/Resource.h>
#include <MyControls/Action.h>
#include <imglib/winimage.h>
//#include "throwWin32Error.h"

namespace My { 

BEGIN_MESSAGE_MAP(BezierEditor, CCmdTarget)
/*	ON_COMMAND(ID_TOOL_BEZIER_DRAW, OnToolDraw)
	ON_UPDATE_COMMAND_UI(ID_TOOL_BEZIER_DRAW, OnUpdateToolDraw)
	ON_COMMAND(ID_TOOL_BEZIER_SELECT, OnToolSelect)
	ON_UPDATE_COMMAND_UI(ID_TOOL_BEZIER_SELECT, OnUpdateToolSelect)
	ON_COMMAND(ID_TOOL_BEZIER_CONVERT, OnToolConvert)
	ON_UPDATE_COMMAND_UI(ID_TOOL_BEZIER_CONVERT, OnUpdateToolConvert)
	ON_COMMAND(ID_TOOL_BEZIER_ADD_POINT, OnToolAddPoint)
	ON_UPDATE_COMMAND_UI(ID_TOOL_BEZIER_ADD_POINT, OnUpdateToolAddPoint)
	ON_COMMAND(ID_TOOL_BEZIER_DELETE_POINT, OnToolDeletePoint)
	ON_UPDATE_COMMAND_UI(ID_TOOL_BEZIER_DELETE_POINT, OnUpdateToolDeletePoint)
*/
END_MESSAGE_MAP()

// BezierEditor

BezierEditor::BezierEditor(ZoomBench & bench) :
	m_Bench(bench), 
	m_pOwner(0), 
	m_Tolerance(4),
	m_DrawTool(* this),
	m_SelectTool(* this),
	m_ConvertTool(* this),
	m_AddPointTool(* this),
	m_DeletePointTool(* this)
{
	m_pTool = &m_SelectTool;
}
	
BezierEditor::~BezierEditor()
{
	assert(!IsOpen());
}
		
void BezierEditor::Open(BezierOwner & owner)
{
	if (IsOpen())
		Close();
	m_pOwner = &owner;
	m_Bench.SetActiveTool(m_pTool);
}

void BezierEditor::Close()
{
	if (!IsOpen())
		return;
	m_pTool = m_Bench.GetActiveTool();
	m_Bench.SetActiveTool(0);
	m_pOwner = 0;
}

void BezierEditor::PaintRect(CDC * pDC, const CRect & r)
{
	if (m_pOwner == 0)
		return;
	for (Size i = 0; i < m_pOwner->GetBezierCount(); ++i) {
		const Bezier & b = m_pOwner->GetBezier(i);
		if (!b.IsVisible())
			continue;
		DrawBezier(pDC, b, true);
		if (!b.IsPartiallySelected())
			DrawSmallNodes(pDC, b);
		else
			DrawNodes(pDC, b);
	}
}

void BezierEditor::PaintRect(CDC * pDC, const CRect & rClient, 
	CRect const & rImage, ByteImage const & im, bool ClipMask, COLORREF background)
{
	if (m_pOwner == 0)
		return;

	bool Clipping = false;
	for (Size i = 0; i < m_pOwner->GetBezierCount(); ++i) {
		const Bezier & b = m_pOwner->GetBezier(i);
		if (b.GetClipping() && b.IsClosed()) {
			Clipping = true;
			break;
		}
	}

	if (!Clipping) {
		img::StretchImage(pDC->m_hDC, 
			rClient.left, rClient.top, rClient.Width(), rClient.Height(),
			rImage.left,  rImage.top,  rImage.Width(),  rImage.Height(), 
			im, SRCCOPY);
		return PaintRect(pDC, rClient);
	}

	CRgn CombinedBezierRgn;
	VERIFY (CombinedBezierRgn.CreateRectRgn(0, 0, 0, 0));

	for (Size i = 0; i < m_pOwner->GetBezierCount(); ++i) {
		const Bezier & b = m_pOwner->GetBezier(i);
		if (!b.GetClipping() || !b.IsClosed())
			continue;
		DrawBezier(pDC, b, false);
		CRgn BezierRgn;
		VERIFY (BezierRgn.CreateFromPath(pDC));
		VERIFY (ERROR != CombinedBezierRgn.CombineRgn(&CombinedBezierRgn, &BezierRgn, RGN_OR));
	}

	VERIFY (ERROR != SelectClipRgn(pDC->m_hDC, HRGN(CombinedBezierRgn)));

	img::StretchImage(pDC->m_hDC, 
		rClient.left, rClient.top, rClient.Width(), rClient.Height(),
		rImage.left,  rImage.top,  rImage.Width(),  rImage.Height(), 
		im, SRCCOPY);

	CRgn ClientRgn;
	VERIFY (ClientRgn.CreateRectRgn(rClient.left, rClient.top, rClient.right, rClient.bottom));

	CRgn InvCombinedBezierRgn;
	VERIFY (InvCombinedBezierRgn.CreateRectRgn(0, 0, 0, 0)); //< need to create region before replacing it
	VERIFY (ERROR != InvCombinedBezierRgn.CombineRgn(&ClientRgn, &CombinedBezierRgn, RGN_DIFF));

	VERIFY (ERROR != SelectClipRgn(pDC->m_hDC, HRGN(InvCombinedBezierRgn)));
	CBrush Brush(background);
	pDC->FillRect(&rClient, &Brush);

	VERIFY (ERROR != SelectClipRgn(pDC->m_hDC, 0)); // restore old clip region

	PaintRect(pDC, rClient);
/*
	for (Size i = 0; i < m_pOwner->GetBezierCount(); ++i) {
		const Bezier & b = m_pOwner->GetBezier(i);

		if (!b.IsClosed())
			DrawBezier(pDC, b, true);

		if (!b.IsPartiallySelected())
			DrawSmallNodes(pDC, b);
		else
			DrawNodes(pDC, b);
	}
*/
}

void BezierEditor::DrawBezier(CDC * pDC, const Bezier & b, bool StrokePath)
{
	std::vector<POINT> pp(3 * (b.GetNodeCount() + 1));
	for (Size i = 0; i < b.GetNodeCount() + 1; ++i) {
		BezierNode n = b.GetNode(i % b.GetNodeCount());
		pp[i * 3 + 0] = GetBench().BenchToClient(n.control[0]);
		pp[i * 3 + 1] = GetBench().BenchToClient(n.position);
		pp[i * 3 + 2] = GetBench().BenchToClient(n.control[1]);
	}

	VERIFY(pDC->BeginPath());
	if (b.IsClosed())
		pDC->PolyBezier(&pp[1], 3 * b.GetNodeCount() + 1);
	else
		pDC->PolyBezier(&pp[1], 3 * b.GetNodeCount() - 2);
	VERIFY(pDC->EndPath());

	if (StrokePath) {
		ByteColor color = b.GetColor();
		CPen pen(PS_SOLID, 1, RGB(color.r, color.g, color.b));
		CPen * pOld = pDC->SelectObject(&pen);

		VERIFY (pDC->StrokePath());

		pDC->SelectObject(pOld);
	}
}

void BezierEditor::DrawNodes(CDC * pDC, const Bezier & b)
{
	ByteColor color = b.GetColor();
	Size i;
	for (i = 0; i < b.GetNodeCount(); ++i) {
		const BezierNode & n = b.GetNode(i);
		DrawNode(pDC, n, b.IsSelected(i), RGB(color.r, color.g, color.b));
	}
	if (!b.IsSelected() || b.GetNodeCount() == 1)
		for (i = 0; i < b.GetNodeCount(); ++i) {
			const BezierNode & n = b.GetNode(i);
			if (n.type == bnFlat)
				continue;
			if (b.IsSelected(Prev(b, i)) || b.IsSelected(i))
				DrawHandle(pDC, n.position, n.control[0], RGB(color.r, color.g, color.b));
			if (b.IsSelected(Next(b, i)) || b.IsSelected(i))
				DrawHandle(pDC, n.position, n.control[1], RGB(color.r, color.g, color.b));
		}
}

void BezierEditor::DrawSmallNodes(CDC * pDC, const Bezier & b)
{
	ByteColor color = b.GetColor();
	for (Size i = 0; i < b.GetNodeCount(); ++i)
		DrawSmallNode(pDC, b.GetNode(i), RGB(color.r, color.g, color.b));
}

void BezierEditor::DrawNode(CDC * pDC, const BezierNode & n, bool selected, COLORREF color)
{
	CPen pen(PS_SOLID, 1, color);
	CBrush brush;
	if (selected)
		brush.CreateSolidBrush(color);
	else
		//brush.CreateSolidBrush(RGB(255, 255, 255));
		brush.CreateStockObject(HOLLOW_BRUSH);

	CPen * pOldPen = pDC->SelectObject(&pen);
	CBrush * pOldBrush = pDC->SelectObject(&brush);

	CPoint p = GetBench().BenchToClient(n.position);
	CRect r = CRect(p, p);
	r.InflateRect(3, 3, 4, 4);

	pDC->Rectangle(&r);

	pDC->SelectObject(pOldPen);
	pDC->SelectObject(pOldBrush);
}

void BezierEditor::DrawSmallNode(CDC * pDC, const BezierNode & n, COLORREF color)
{
	CPen pen(PS_SOLID, 1, color);
	CBrush brush(color);

	CPen * pOldPen = pDC->SelectObject(&pen);
	CBrush * pOldBrush = pDC->SelectObject(&brush);

	CPoint p = GetBench().BenchToClient(n.position);
	CRect r = CRect(p, p);
	r.InflateRect(1, 1, 2, 2);

	pDC->Rectangle(&r);

	pDC->SelectObject(pOldPen);
	pDC->SelectObject(pOldBrush);
}

void BezierEditor::DrawHandles(CDC * pDC, const Bezier & b, int index)
{
	if (index == -1)
		return;

	ByteColor color = b.GetColor();
	const BezierNode & n = b.GetNode(Size(index));
	if (n.type != bnFlat) {
		DrawHandle(pDC, n.position, n.control[0], RGB(color.r, color.g, color.b));
		DrawHandle(pDC, n.position, n.control[1], RGB(color.r, color.g, color.b));
	}
	if (b.IsClosed() || index > 0) {
		const BezierNode & n = b.GetNode(Prev(b, index));
		if (n.type != bnFlat)
			DrawHandle(pDC, n.position, n.control[1], RGB(color.r, color.g, color.b));
	}
	if (b.IsClosed() || index < int(b.GetNodeCount()) - 1) {
		const BezierNode & n = b.GetNode(Next(b, index));
		if (n.type != bnFlat)
			DrawHandle(pDC, n.position, n.control[0], RGB(color.r, color.g, color.b));
	}
}

void BezierEditor::DrawHandle(CDC * pDC, const point<float> & p, const point<float> & c, COLORREF color)
{
	CPen pen(PS_SOLID, 1, color);
	CBrush brush(color);

	CPen * pOldPen = pDC->SelectObject(&pen);
	CBrush * pOldBrush = pDC->SelectObject(&brush);

	pDC->MoveTo(GetBench().BenchToClient(p));
	pDC->LineTo(GetBench().BenchToClient(c));	
	
	CPoint c1 = GetBench().BenchToClient(c);
	CRect r = CRect(c1, c1);
	r.InflateRect(2, 2, 3, 3);
	pDC->Ellipse(&r);

	pDC->SelectObject(pOldPen);
	pDC->SelectObject(pOldBrush);
}

bool BezierEditor::ControlPointAt(CPoint p, Size & icurve, Size & inode, Size & icontrol)
{
	if (!IsOpen())
		return false;

	CRect r(p, p);
	r.InflateRect(GetTolerance(), GetTolerance());
	rect<float> fr = GetBench().ClientToBench(r);

	for (Size i = 0; i < GetBezierOwner()->GetBezierCount(); ++i) {
		const Bezier & b = GetBezierOwner()->GetBezier(i);
		if (!b.IsVisible())
			continue;
		if (b.IsPartiallySelected()) {
			if (b.IsSelected() && b.GetNodeCount() != 1)
				continue;
			for (Size j = 0; j < b.GetNodeCount(); ++j) {
				const BezierNode & n = b.GetNode(j);
				if (n.type == bnFlat)
					continue;
				if (b.IsSelected(Prev(b, j)) || b.IsSelected(j))
					if (fr.inside(n.control[0])) {
						icurve = i;
						inode = j;
						icontrol = 0;
						return true;
					}
				if (b.IsSelected(Next(b, j)) || b.IsSelected(j))
					if (fr.inside(n.control[1])) {
						icurve = i;
						inode = j;
						icontrol = 1;
						return true;
					}
			}
		}
	}

	return false;
}

bool BezierEditor::NodeAt(CPoint p, Size & icurve, Size & inode)
{
	if (!IsOpen())
		return false;

	CRect r(p, p);
	r.InflateRect(GetTolerance(), GetTolerance());
	rect<float> fr = GetBench().ClientToBench(r);

	// look through selection first!

	for (Size i = 0; i < GetBezierOwner()->GetBezierCount(); ++i) {
		const Bezier & b = GetBezierOwner()->GetBezier(i);
		if (!b.IsVisible())
			continue;
		for (Size j = 0; j < b.GetNodeCount(); ++j)
			if (b.IsSelected(j) && fr.inside(b.GetNode(j).position)) {
				icurve = i;
				inode = j;
				return true;
			}
	}

	for (Size i = 0; i < GetBezierOwner()->GetBezierCount(); ++i) {
		const Bezier & b = GetBezierOwner()->GetBezier(i);
		if (!b.IsVisible())
			continue;
		for (Size j = 0; j < b.GetNodeCount(); ++j)
			if (fr.inside(b.GetNode(j).position)) {
				icurve = i;
				inode = j;
				return true;
			}
	}

	return false;
}

bool BezierEditor::CurveAt(CPoint p, Size & icurve, float & t)
{
	if (!IsOpen())
		return false;

	float tolerance = GetTolerance() / GetBench().GetScale();
	float accuracy = 1.f / GetBench().GetScale();

	float min = tolerance;
	for (Size i = 0; i < GetBezierOwner()->GetBezierCount(); ++i) {
		Bezier & b = GetBezierOwner()->GetBezier(i);
		if (!b.IsVisible())
			continue;
		Size last = b.IsClosed() ? b.GetNodeCount() : b.GetNodeCount() - 1;
		for (Size j = 0; j < last; ++j) {
			BezierNode & n0 = b.GetNode(j);
			BezierNode & n1 = b.GetNode(Next(b, j));
			float tmp, d;
			if (BezierNearest(
				n0.position,
				n0.control[1],
				n1.control[0],
				n1.position,
				GetBench().ClientToBench(p), accuracy, tolerance, d, tmp)) 
			{
				if (d < min) {
					min = d;
					icurve = i;
					t = float(j) + tmp;
				}
			}
		}
	}
	return min < tolerance;
}

void BezierEditor::Nudge(CPoint dp)
{
	My::MultiAction nudge("Nudge");

	point<float> d;
	d.x = float(dp.x) / m_Bench.GetScale();
	d.y = float(dp.y) / m_Bench.GetScale();
	for (Size i = 0; i < GetBezierOwner()->GetBezierCount(); ++i) {
		Bezier & b = GetBezierOwner()->GetBezier(i);
		for (Size j = 0; j < b.GetNodeCount(); ++j) 
			if (b.IsSelected(j)) {
				BezierNode n = b.GetNode(j);
				n.position += d;
				n.control[0] += d;
				n.control[1] += d;
				b.SetNode(j, n);
			}
	}
}

void BezierEditor::OnToolDraw()
{
	if (!IsOpen())
		return;
	m_Bench.SetActiveTool(Draw());
}

void BezierEditor::OnUpdateToolDraw(CCmdUI * pCmdUI)
{
	pCmdUI->SetCheck(m_Bench.GetActiveTool() == Draw());
	pCmdUI->Enable(IsOpen() ? 1 : 0);
}

void BezierEditor::OnToolSelect()
{
	if (!IsOpen())
		return;
	m_Bench.SetActiveTool(Select());
}

void BezierEditor::OnUpdateToolSelect(CCmdUI * pCmdUI)
{
	pCmdUI->SetCheck(m_Bench.GetActiveTool() == Select());
	pCmdUI->Enable(IsOpen() ? 1 : 0);
}

void BezierEditor::OnToolConvert()
{
	if (!IsOpen())
		return;
	m_Bench.SetActiveTool(Convert());
}

void BezierEditor::OnUpdateToolConvert(CCmdUI * pCmdUI)
{
	pCmdUI->SetCheck(m_Bench.GetActiveTool() == Convert());
	pCmdUI->Enable(IsOpen() ? 1 : 0);
}

void BezierEditor::OnToolAddPoint()
{
	if (!IsOpen())
		return;
	m_Bench.SetActiveTool(AddPoint());
}

void BezierEditor::OnUpdateToolAddPoint(CCmdUI * pCmdUI)
{
	pCmdUI->SetCheck(m_Bench.GetActiveTool() == AddPoint());
	pCmdUI->Enable(IsOpen() ? 1 : 0);
}

void BezierEditor::OnToolDeletePoint()
{
	if (!IsOpen())
		return;
	m_Bench.SetActiveTool(DeletePoint());
}

void BezierEditor::OnUpdateToolDeletePoint(CCmdUI * pCmdUI)
{
	pCmdUI->SetCheck(m_Bench.GetActiveTool() == DeletePoint());
	pCmdUI->Enable(IsOpen() ? 1 : 0);
}

} // My